package org.cg;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.IOException;
import java.util.SortedSet;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.glu.GLU;
import org.cg.entidade.Entidade;
import org.cg.entidade.Prado;
import org.cg.gestor.Gestores;
import org.cg.gestor.Mundo;

public class CoelhosEventListener implements GLEventListener, MouseListener, MouseMotionListener, MouseWheelListener, KeyListener {
	
	private final static float BASE_X_Y_Z_ROTATION = 2.0f;
	private final static float BASE_Z_ZOOM = 0.5f;
	private final static String TEXTURE_GRASS = "textures/grass.jpg";
	
	private int pradoSize;
	private GLU glu;
	private int prevMouseX, prevMouseY = 0;
	private boolean lMouseButtonClicked, rMouseButtonClicked;
	private float rotX, rotY, rotZ = 0.0f;
	private boolean leftRotateZ, rightRotateZ = false;
	private float camZ = 2.5f;
	private int grassTexture;
	
	public void init(GLAutoDrawable drawable) {
		glu = new GLU();
		
		GL2 gl = drawable.getGL().getGL2();
        gl.glShadeModel(GL2.GL_SMOOTH);             // Enable Smooth Shading
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 	// Black Background
        gl.glClearDepth(1.0f);                      // Depth Buffer Setup
        gl.glEnable(GL.GL_DEPTH_TEST);              // Enables Depth Testing
        gl.glDepthFunc(GL.GL_LEQUAL);               // The Type Of Depth Testing To Do
        // Really Nice Perspective Calculations
        gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
        
        //prepare the textures
        setUpTextures(gl);
	}
	
	private void setUpTextures(GL2 gl) {
		//enable, generate and bind the texture
		gl.glEnable(GL.GL_TEXTURE_2D);
        grassTexture = generateTexture(gl);
        gl.glBindTexture(GL.GL_TEXTURE_2D, grassTexture);
        
        //load image file
        TextureReader.Texture texture = null;
        try {
            texture = TextureReader.readTexture(TEXTURE_GRASS);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        
        //make RGB texture
        gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB, texture.getWidth(), 
        		texture.getHeight(), 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, 
        		texture.getPixels());

        //look parameters
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
        gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
	}
	
	private int generateTexture(GL gl) {
        final int[] tmp = new int[1];
        gl.glGenTextures(1, tmp, 0);
        return tmp[0];
    }
	
	public void display(GLAutoDrawable drawable) {
		Mundo mundo = Gestores.getGestorMundo();
		if (mundo.getEntidades().size() <= 1) {//so tenho o prado
			dispose(drawable);
			return;
		}
		//mundo.update();
	    render(drawable, mundo.getEntidades());
	}
	
	private void render(GLAutoDrawable drawable, SortedSet<Entidade> entidades) {
	    GL2 gl = drawable.getGL().getGL2();
	    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
	    gl.glLoadIdentity();
	    
	    //bind grass grassTexture
	    gl.glBindTexture(GL.GL_TEXTURE_2D, grassTexture);
	    
	    //start camera
	    setCamera(drawable);
	    
	    //start the lightning
	    setLightning(gl);
        
	    //draw the simulation entities
	    for (Entidade entidade : entidades) {
	    	
	    	if (entidade instanceof Prado) {
	    		Prado prado = (Prado)entidade;
	    		pradoSize = prado.getLimites().getxMax();
	    		drawPrado(drawable);
	    	}
	    	
	    }
	    
	    //flush the buffer to screen
	    gl.glFlush();
	}
	
	private void setCamera(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		
		// Change to projection matrix.
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();

        // Perspective.
        glu.gluPerspective(45, 1, 1, 1000);
        glu.gluLookAt(0, 0, camZ, 0, 0, 0, 0, 1, 0);
        
        //rotations
        gl.glRotatef(rotX, 1.0f, 0.0f, 0.0f);
        gl.glRotatef(rotY, 0.0f, 1.0f, 0.0f);
        if (leftRotateZ) {
        	rotZ -= BASE_X_Y_Z_ROTATION;
        }
        if (rightRotateZ) {
        	rotZ += BASE_X_Y_Z_ROTATION;
        }
        gl.glRotatef(rotZ, 0.0f, 0.0f, 1.0f);

        // Change back to model view matrix.
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
	}
	
	private void setLightning(GL2 gl) {
		// Prepare light parameters.
        float SHINE_ALL_DIRECTIONS = 1;
        float[] lightPos = {-30, 0, 30, SHINE_ALL_DIRECTIONS};
        float[] lightColorAmbient = {0.4f, 0.4f, 0.4f, 1f};
        float[] lightColorSpecular = {0.5f, 0.5f, 0.5f, 1f};

        // Set light parameters.
        gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_POSITION, lightPos, 0);
        gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_AMBIENT, lightColorAmbient, 0);
        gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_SPECULAR, lightColorSpecular, 0);

        // Enable lighting in GL.
        gl.glEnable(GL2.GL_LIGHT1);
        gl.glEnable(GL2.GL_LIGHTING);

        // Set material properties.
        float[] rgba = {0.5f, 1.0f, 0.5f};
        gl.glMaterialfv(GL.GL_FRONT, GL2.GL_AMBIENT, rgba, 0);
        gl.glMaterialfv(GL.GL_FRONT, GL2.GL_SPECULAR, rgba, 0);
	}
	
	private void drawPrado(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		
		float[] color = new float[3];
		
		//min between width and height
		int min = Math.min(drawable.getWidth(), drawable.getHeight());
		
		//square size
		float xSize = (min / (float)pradoSize) * 2.0f / (float)drawable.getWidth();
		float ySize = (min / (float)pradoSize) * 2.0f / (float)drawable.getHeight();
		
		//calculate remaining space from Width or Height
		//translate to the X and Y starting positions
		if (min == drawable.getWidth()) { // Width is smaller
			float remainingSpace = (drawable.getHeight() - min) / (float)drawable.getHeight();
			gl.glTranslatef(-1.0f, 1.0f - remainingSpace, 0.0f);
		} else if (min == drawable.getHeight()) { // Height is smaller
			float remainingSpace = (drawable.getWidth() - min) / (float)drawable.getWidth();
			gl.glTranslatef(-1.0f + remainingSpace, 1.0f, 0.0f);
		}
		
		for (int i = 0 ; i < pradoSize ; i ++) {
			for (int j = 0; j < pradoSize; j ++) {
				if ((i+j) % 2 == 0) {
					color[0] = 0.31f; color[1] = 0.61f; color[2] = 0.91f; //light blue
				} else {
					color[0] = 0.91f; color[1] = 0.45f; color[2] = 0.0f; //orange
				}
				drawSquare(gl, color, xSize, ySize);
				
				//translate to the next X placing spot
				gl.glTranslatef(xSize, 0.0f, 0.0f);
			}
			//translate to the next Y placing spot and to initial X
			gl.glTranslatef(-xSize*pradoSize, -ySize, 0.0f);
		}
		
		//translate to the initial X and Y
		gl.glTranslatef(0.0f, ySize*pradoSize, 0.0f);
	}
	
	private void drawSquare(GL2 gl, float[] color, float xSize, float ySize) {
		gl.glBegin(GL.GL_TRIANGLES);
		
		gl.glTexCoord2f(0.0f, 1.0f);
		gl.glVertex3f(0.0f, 0.0f, 0.0f);	//left upper
		
		gl.glTexCoord2f(0.0f, 0.0f);
		gl.glVertex3f(0.0f, -ySize, 0.0f);	//left lower
		
		gl.glTexCoord2f(1.0f, 1.0f);
		gl.glVertex3f(xSize, 0.0f, 0.0f);	//right upper
		
		gl.glTexCoord2f(0.0f, 0.0f);
		gl.glVertex3f(0, -ySize, 0.0f);		//left lower
		
		gl.glTexCoord2f(1.0f, 1.0f);
		gl.glVertex3f(xSize, 0.0f, 0.0f);	//right upper
		
		gl.glTexCoord2f(1.0f, 0.0f);
		gl.glVertex3f(xSize, -ySize, 0.0f);	//right lower
		
		gl.glEnd();
	}

	public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
		GL2 gl = drawable.getGL().getGL2();
        
		if (h <= 0) { // avoid a divide by zero error!
	        h = 1;
		}
	    
	    gl.glViewport(0, 0, w, h);
	    gl.glMatrixMode(GL2.GL_PROJECTION);
	    gl.glLoadIdentity();
	    float aspectRatio = (float)w / (float)h;
	    glu.gluPerspective(45.0f, aspectRatio, 1, 1000);
	    
	    gl.glMatrixMode(GL2.GL_MODELVIEW);
	    gl.glLoadIdentity();
	}

	public void dispose(GLAutoDrawable drawable) {
		// TODO Auto-generated method stub
		
	}

	public void mouseDragged(MouseEvent e) {
		GLCanvas canvas = (GLCanvas) e.getComponent();
		
		//get the new mouse coordinates
		int newMouseX = e.getX();
		int newMouseY = e.getY();
		
		//left mouse button clicked
		if (lMouseButtonClicked) {
			float diffX = (float)(newMouseX - prevMouseX);
			float diffY = (float)(newMouseY - prevMouseY);
			
			//check X movements
			if (diffX > 0.0f) {
				rotY += BASE_X_Y_Z_ROTATION;
			} else if (diffX < 0.0f) {
				rotY -= BASE_X_Y_Z_ROTATION;
			}
			
			//check Y movements
			if (diffY > 0.0f) {
				rotX += BASE_X_Y_Z_ROTATION;
			} else if (diffY < 0.0f) {
				rotX -= BASE_X_Y_Z_ROTATION;
			}
		} else if (rMouseButtonClicked) { //right mouse button clicked
			//TODO picking
		}
		
		//redisplay
		canvas.display();
		
		//save the new mouse coordinates
		prevMouseX = newMouseX;
		prevMouseY = newMouseY;
	}
	
	public void mousePressed(MouseEvent e) {
		//save the new mouse coordinates
		prevMouseX = e.getX();
		prevMouseY = e.getY();
		
		//left mouse button clicked
		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
			lMouseButtonClicked = true;
		}
		
		//right mouse button clicked
		if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
			rMouseButtonClicked = true;
		}
	}

	public void mouseReleased(MouseEvent e) {
		//left mouse button released
		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
			lMouseButtonClicked = false;
		}
				
		//right mouse button released
		if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
			rMouseButtonClicked = false;
		}
	}
	
	public void mouseWheelMoved(MouseWheelEvent e) {
		GLCanvas canvas = (GLCanvas) e.getComponent();
		
		//increment/decrement the camZ position
		int wheelRotation = e.getWheelRotation();
		if (wheelRotation > 0) {
			camZ += BASE_Z_ZOOM;
		} else if (wheelRotation < 0) {
			camZ -= BASE_Z_ZOOM;
		}
		
		//no zoom more than 1.0f
		if (camZ < 1.0f) {
			camZ = 1.0f;
		}
		
		//redisplay
		canvas.display();
	}

	public void keyPressed(KeyEvent e) {
		//Pressed A
		if (e.getKeyCode() == KeyEvent.VK_A) {
			leftRotateZ = true;
		}
		
		//Pressed D
		if (e.getKeyCode() == KeyEvent.VK_D) {
			rightRotateZ = true;
		}
	}
	
	public void keyReleased(KeyEvent e) {
		//Released A
		if (e.getKeyCode() == KeyEvent.VK_A) {
			leftRotateZ = false;
		}
		
		//Released D
		if (e.getKeyCode() == KeyEvent.VK_D) {
			rightRotateZ = false;
		}
	}
	
	public void mouseClicked(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mouseMoved(MouseEvent e) {}
	public void keyTyped(KeyEvent e) {}
}
